package com.ly.crawl.util;

import com.google.gson.Gson;
import com.ly.crawl.DataDao;
import com.ly.crawl.impl.SimpleDataDao;
import ognl.Ognl;
import ognl.OgnlException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.Map.Entry;

public class Utils {
    /**
     * 返回跟目录 例如: http://127.0.0.1/a/b/c/index.html => http://127.0.0.1/
     */
    public static String getRootURL(String url) {
        if (url.startsWith("http") == false) {
            return null;
        }
        try {
            URL u = new URL(url);
            return u.getProtocol() + "://" + u.getAuthority() + "/";
        } catch (MalformedURLException e) {
            throw new RuntimeException("地址错误!", e);
        }
    }

    /**
     * 返回 当前路径 例如: http://127.0.0.1/a/b/c/index.html => http://127.0.0.1/a/b/c/
     */
    public static String getBaseURL(String url) {
        URL u = null;
        try {
            u = new URL(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        return url.substring(url.lastIndexOf("/")+1);
    }

    /**
     * 返回真实路径
     * @param url
     * @param path  例如: http:...   /a/b/c.html   ../../a.html
     * @return
     */
    public static String getRealURL(String url, String path) {
        if (path.startsWith("http")) {
            return path;
        }
        if (path.startsWith("//")) {
            return url.substring(0, url.indexOf("//")) + path;
        }
        if(path.startsWith("/")){
            String rootURL = getRootURL(url);
            return rootURL + path.replaceAll("/+(.+)","$1");
        }
        String baseURL = getBaseURL(url);
        if (path.startsWith("../")) {
            String suffixPath = baseURL;
            while (path.startsWith("../") || path.startsWith("./")) {
                if (path.startsWith("../")) {
                    suffixPath = suffixPath.replaceAll("(.+/[^/]+)/+","$1");
                    path = path.substring(3);
                } else {
                    path = path.substring(2);
                }
            }
            return suffixPath + path;
        }
        return baseURL + path;
    }

    /**
     * Jsoup 对 post 请求有限制, 所以自定义post请求
     *
     * @param url
     * @param data
     * @return
     * @throws IOException
     */
    public static String post(String url, String... data) throws IOException {
        URL realUrl = new URL(url);
        // 打开和URL之间的连接
        URLConnection conn = realUrl.openConnection();
        //设置超时时间
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(15000);
        // 接收数据格式 : 无限
        conn.setRequestProperty("accept", "*/*");
        // AJAX 请求头
        conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");
        // 表单提交数据编码
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        // 浏览器信息
        conn.setRequestProperty("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
        // 发送POST请求必须设置如下两行
        conn.setDoOutput(true);
        conn.setDoInput(true);

        // 发送请求参数
        StringBuilder param = new StringBuilder();
        for (int i = 0; i < data.length; i += 2) {
            param.append(data[i] + "=" + data[i + 1] + "&");
        }

        try (PrintWriter out = new PrintWriter(conn.getOutputStream());
             BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf8"))) {
            out.print(param);
            out.flush();
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            return result.toString();
        }
    }

    /**
     * 下载网页, 保存 js, css, img 到本地目录
     *
     * @param url
     * @param filepath
     * @throws IOException
     */
    public static void saveHTML(String url, String filepath) throws IOException {
        if (url == null || url.trim().isEmpty()) {
            return;
        }
        Document doc = Jsoup.parse(new URL(url), 5000);
        saveHTML(doc, url, filepath);
    }

    public static void saveHTML(String url, String htmlpath, String filepath) throws IOException {
        if (url == null || url.trim().isEmpty()) {
            return;
        }
        Document doc = Jsoup.parse(new File(htmlpath), "utf-8");
        saveHTML(doc, url, filepath);
    }

    private static void saveHTML(Document doc, String url, String filepath) throws IOException {
        String rootURL = getRootURL(url);
        String baseURL = getBaseURL(url);
        String basedir = new File(filepath).getParent();
        saveFile(doc, rootURL, baseURL, basedir, "script", "src");
        saveFile(doc, rootURL, baseURL, basedir, "link", "href");
        saveFile(doc, rootURL, baseURL, basedir, "img", "src");
        File f = new File(filepath);
        if (f.getParentFile().exists() == false) {
            f.getParentFile().mkdirs();
        }
        try (FileWriter fr = new FileWriter(filepath)) {
            fr.write(doc.toString());
        }
    }

    private static void saveFile(Document doc, String rootURL, String baseURL, String basedir, String tag,
                                 String attr) {
        Elements es = doc.select(tag + "[" + attr + "]");
        for (int i = 0; i < es.size(); i++) {
            Element e = es.get(i);
            String src = e.attr(attr);
            if (src.startsWith("http")) {
                // 使用原地址, 什么都不做
            } else if (src.startsWith("//")) {
                String p = baseURL.substring(0, baseURL.indexOf("//"));
                src = getRealURL(rootURL, p + src);
            } else if (src.startsWith("/")) {
                src = getRealURL(rootURL, src);
            } else {
                src = getRealURL(baseURL, src);
            }
            try {
                String path = download(src, basedir);
                System.out.println(e.attr("src") + " --> " + path);
                e.attr(attr, path);
            } catch (IOException ex) {
                System.out.println("下载资源失败: " + e.attr("src"));
                ex.printStackTrace();
            }
        }
    }

    public static String download(String url, String basedir) throws IOException {
        URL u = new URL(url);
        File baseFile = new File(basedir);
        File file = new File(baseFile, u.getPath());
        if (file.getParentFile().exists() == false && file.getParentFile().mkdirs() == false) {
            throw new IOException("创建下载目录失败: " + file.getParent());
        }
        System.out.println(url + " ==> " + file);
        try (InputStream in = u.openStream(); FileOutputStream fos = new FileOutputStream(file)) {
            byte[] buf = new byte[1024];
            int count;
            while ((count = in.read(buf)) > 0) {
                fos.write(buf, 0, count);
            }
        }
        return file.getPath().replace(baseFile.getPath(), "").replaceAll("^\\\\+", "").replaceAll("\\\\+", "/");
    }

    public static void downloadByFile(String baseURL, String basedir) throws IOException {
        String filepath = basedir + "/download.idx";
        List<String> lines = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader(filepath));) {
            String line;
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }
        }
        try (PrintWriter pw = new PrintWriter(new FileWriter(filepath))) {
            for (String line : lines) {
                String realURL = null;
                try {
                    if (line.matches(".*[^']?file:///.+")) {
                        line = line.replaceAll(".*(file:///" + basedir + "\\S+).*", "$1");
                        String path = line.substring(("file:///" + basedir).length());
                        realURL = getRealURL(baseURL, path);
                        download(realURL, basedir);
                    } else if (line.matches(".*[^']?https?://.+")) {
                        realURL = line.replaceAll(".*(https?://\\S+).*", "$1");
                        download(realURL, basedir);
                    }
                } catch (IOException e) {
                    System.out.println(" X 下载失败 : " + realURL);
                    pw.println(line);
                }
            }
        }

    }

    public static void replaceAttr(String filepath, String tag, String attr, String regex, String replacement)
            throws IOException {
        replaceAttr(filepath, "UTF-8", tag, attr, regex, replacement);
    }

    public static void replaceAttr(String filepath, String charset, String tag, String attr, String regex,
                                   String replacement) throws IOException {
        verify(filepath, charset, tag + "[" + attr + "]", e -> {
            String s = e.attr(attr);
            if (s.matches(regex)) {
                String news = s.replaceAll(regex, replacement);
                System.out.println(s + " ==> " + news);
                e.attr(attr, news);
            }
        });
    }

    public static void verify(String filepath, String cssSelector, ElementHandler eh) throws IOException {
        verify(filepath, "utf-8", cssSelector, eh);
    }

    public static void verify(String filepath, String charset, String cssSelector, ElementHandler eh)
            throws IOException {
        if (filepath.contains("*")) {
            File file = new File(filepath);
            String regex = file.getName().replaceAll("\\*", ".*");
            for (File f : file.getParentFile().listFiles(ff -> {
                return ff.getName().matches(regex);
            })) {
                verify(f.getPath(), charset, cssSelector, eh);
            }
        } else {
            System.out.printf("更新开始 %s : %s\n", filepath, cssSelector);
            Document doc = Jsoup.parse(new File(filepath), charset);
            Elements es = doc.select(cssSelector);
            for (int i = 0; i < es.size(); i++) {
                Element ele = es.get(i);
                System.out.printf("   修改 %s\n", ele);
                eh.process(ele);
                System.out.printf("   ==> %s\n\n", ele);
            }
            try (FileWriter fr = new FileWriter(filepath)) {
                fr.write(doc.toString());
            }
            System.out.printf("更新完成 %s : %s\n", filepath, cssSelector);
        }
    }

    public static interface ElementHandler {
        void process(Element e);
    }

    public static String DEFAULT_PARENT_SEPARATOR_REGEX = "\\^{3}";

    public static boolean isParentCssQuery(String cssQuery) {
        return cssQuery.matches(".+" + DEFAULT_PARENT_SEPARATOR_REGEX + ".+");
    }

    public static Element getParentOne(Element e, String cssQuery) {
        return getParentOne(e, cssQuery, DEFAULT_PARENT_SEPARATOR_REGEX);
    }

    public static Element getParentOne(Element e, String cssQuery, String regex) {
        String[] ss = cssQuery.split(regex);
        System.out.println(Arrays.toString(ss));
        if (ss.length > 1) {
            for (Element ele : e.selectFirst(ss[0]).parents()) {
                if (ele.equals(ele.selectFirst(ss[1]))) {
                    return ele;
                }
            }
        }
        return null;
    }

    public static List<Element> getParentList(Element e, String cssQuery) {
        return getParentList(e, cssQuery, DEFAULT_PARENT_SEPARATOR_REGEX);
    }

    public static List<Element> getParentList(Element e, String cssQuery, String regex) {
        List<Element> ret = new ArrayList<>();
        String[] ss = cssQuery.split(regex);
        if (ss.length > 1) {
            for (Element ce : e.select(ss[0])) {
                for (Element el : ce.parents()) {
                    if (el.equals(el.selectFirst(ss[1]))) {
                        ret.add(el);
                        break;
                    }
                }
            }
        }
        return ret;
    }

//    public static void downloadForTable(String basedir, String table, String column) {
//        int page = 1;
//        int rows = 1000;
//        String sql = String.format("select distinct %s,url,id from %s where length(%s)>0 limit ?,?", column, table, column);
//        while (true) {
//            List<Map<String, Object>> maps = DBHelper.selectListMap(sql, (page - 1) * rows, rows);
//            if (maps.isEmpty()) {
//                return;
//            }
//            maps.forEach(m -> {
//                String img = (String) m.get(column);
//                String url = (String) m.get("url");
//                try {
//                    String baseURL = getBaseURL(url);
//                    String realURL = getRealURL(baseURL, img);
//                    String webPath = download(realURL, basedir);
//                    String update = "update %s set %s=? where id=?";
//                    update = String.format(update, table, column);
//                    DBHelper.update(update, webPath, m.get("id"));
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            page++;
//        }
//
//    }

    @SuppressWarnings("unchecked")
    public static void ajaxToTable(String url, int maxPage, String table, String ognlExp, Object... fieldMappers) throws IOException {
        DataDao<Map<String, Object>> dao = new SimpleDataDao(table);
        Gson gson = new Gson();
        Map<Object, Object> fmMap = Utils.arrayToMap(fieldMappers);
        for (int i = 1; i <= maxPage; i++) {
            String u = String.format(url, i);
            String ret = Utils.post(u);
            Object json = gson.fromJson(ret, Object.class);
            List<Map<String, Object>> list;
            list = getValue(json, ognlExp);
            for (Map<String, Object> m : list) {
                fmMap.entrySet().forEach(e -> {
                    if (e.getValue() instanceof String) {
                        String value = (String) e.getValue();
                        if (value.matches("TABLE:\\w+")) {
                            String t = value.replaceAll("TABLE:(\\w+)", "$1");
                            e.setValue(new SimpleDataDao(t));
                        } else {
                            m.put((String) e.getKey(), getValue(m, value));
                        }
                    }
                    if (e.getValue() instanceof DataDao) {
                        DataDao<Map<String, Object>> dd = (DataDao<Map<String, Object>>) e.getValue();
                        dd.save((Map<String, Object>) m.get(e.getKey()));
                    }
                });
                Iterator<Entry<String, Object>> it = m.entrySet().iterator();
                while (it.hasNext()) {
                    Entry<String, Object> e = it.next();
                    if (e.getValue() != null && (e.getValue() instanceof Collection || e.getValue() instanceof Map)) {
                        it.remove();
                    }
                }
                dao.save(m);
            }
        }
    }

    private static <T> Map<T, T> arrayToMap(T[] arr) {
        Map<T, T> ret = new LinkedHashMap<>();
        for (int i = 0; i < arr.length - 1; i += 2) {
            ret.put(arr[i], arr[i + 1]);
        }
        return ret;
    }

    public static <T> T getValue(Object obj, String exp) {
        return getValue(obj, exp, null);
    }

    public static <T> T getValue(Object obj, String exp, T defaultValue) {
        try {
            T t = (T) Ognl.getValue(exp, obj);
            if (t == null) {
                return defaultValue;
            } else {
                return t;
            }
        } catch (OgnlException e) {
            System.out.printf("OGNL 对象 : %s\nOGNL 表达式 : %s\n", obj, exp);
            throw new RuntimeException(e);
        }
    }

    public static <T> T get(Handler1<T> h, Object... params) {
        try {
            return h.get(params);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("执行错误", e);
        }
    }

    public static <T> T give(Handler1<T> h, T defaultValue, Object... params) {
        try {
            return get(h, params);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static void exec(Handler2 h, Object... params) {
        try {
            h.exec(params);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("执行错误", e);
        }
    }

    public static interface Handler1<T> {
        T get(Object... params) throws Exception;
    }

    public static interface Handler2 {
        void exec(Object... params) throws Exception;
    }

    public static boolean in(Object value, Object... vals) {
        for (Object val : vals) {
            if (value == null) {
                if (val == null) {
                    return true;
                }
            } else if (value.equals(val)) {
                return true;
            }
        }
        return false;
    }

    public static int getInt(Object... vals) {
        for (Object val : vals) {
            if (val instanceof Integer) {
                return (int) val;
            } else {
                try {
                    if (val != null) {
                        Integer.parseInt(val.toString());
                    }
                } catch (NumberFormatException e) {
                    continue;
                }
            }
        }
        throw new NoSuchElementException("没有合法的int值");
    }
}
